/*
 * main.c
 *
 *  Created on: 14.11.2011
 *      Author: Pavel Sinay
 */

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <string.h>
#include <poll.h>
#include <ifaddrs.h>
#include <linux/if_arp.h>
#include <netdb.h>

struct arp_packet_t {
	__u16 htype;
	__u16 ptype;
	__u8 hlen;
	__u8 plen;
	__u16 oper;
	__u8 sha[6];
	__u8 spa[4];
	__u8 tha[6];
	__u8 tpa[4];
};

int main(int argc, char* argv[]) {
	int fd_socket;
	char *interface = 0;
	in_addr_t ip;
	struct arp_packet_t arp_req;
	struct arp_packet_t arp_resp;
	struct ifaddrs *if_addrs, *if_iter;
	struct sockaddr_ll *addr_ll = 0;
	struct sockaddr_in *addr_in = 0;
	struct pollfd pfd;

	printf("ARP utility\n");

	if (argc < 2) {
		printf("Usage: <IP>\n");
		return 1;
	}

	inet_aton(argv[1], &ip);
	getifaddrs(&if_addrs);
	for (if_iter = if_addrs; if_iter != NULL; if_iter = if_iter->ifa_next) {
		if (if_iter->ifa_addr->sa_family == AF_INET) {
			in_addr_t if_ip =
					((struct sockaddr_in*) if_iter->ifa_addr)->sin_addr.s_addr;
			in_addr_t
					if_mask =
							((struct sockaddr_in*) if_iter->ifa_netmask)->sin_addr.s_addr;
			if ((if_ip & if_mask) == (ip & if_mask)) {
				interface = if_iter->ifa_name;
				addr_in = (struct sockaddr_in*) if_iter->ifa_addr;
				break;
			}
		}
	}
	if (interface) {
		for (if_iter = if_addrs; if_iter != NULL; if_iter = if_iter->ifa_next) {
			if (!strcmp(interface, if_iter->ifa_name)
					&& if_iter->ifa_addr->sa_family == AF_PACKET) {
				addr_ll = (struct sockaddr_ll*) if_iter->ifa_addr;
				addr_ll->sll_protocol = htons(ETH_P_ARP);
				break;
			}
		}
	}

	if (!addr_ll) {
		perror("Can not find interface\n");
		return 1;
	}

	if ((fd_socket = socket(AF_PACKET, SOCK_DGRAM, ETH_P_ARP)) < 0) {
		perror("Failed to create socket\n");
		return 1;
	}
	if (bind(fd_socket, (struct sockaddr*) addr_ll, sizeof(struct sockaddr_ll))
			< 0) {
		perror("failed to bind\n");
		return 1;
	}

	struct sockaddr_ll broadcast;
	broadcast.sll_family = AF_PACKET;
	broadcast.sll_protocol = htons(ETH_P_ARP);
	broadcast.sll_ifindex = addr_ll->sll_ifindex;
	broadcast.sll_hatype = ARPHRD_ETHER;
	broadcast.sll_pkttype = PACKET_BROADCAST;
	broadcast.sll_halen = ETH_ALEN;
	memset(broadcast.sll_addr, 0xFF, ETH_ALEN);

	arp_req.htype = htons(0x0001);
	arp_req.ptype = htons(0x0800);
	arp_req.hlen = 6;
	arp_req.plen = 4;
	arp_req.oper = htons(1);
	memcpy(arp_req.sha, addr_ll->sll_addr, 6);
	memcpy(arp_req.spa, &addr_in->sin_addr, 4);
	memset(arp_req.tha, 0, ETH_ALEN);
	memcpy(arp_req.tpa, &ip, 4);

	if (sendto(fd_socket, &arp_req, sizeof(struct arp_packet_t), 0,
			(struct sockaddr*) &broadcast, sizeof(struct sockaddr_ll)) < 0) {
		perror("sendto\n");
		return 1;
	}

	pfd.fd = fd_socket;
	pfd.events = POLLIN;
	if (poll(&pfd, 1, 1000) <= 0) {
		printf("Timeout\n");
		return 1;
	}

	if (recv(fd_socket, &arp_resp, sizeof(struct arp_packet_t), 0) < 0) {
		perror("recv\n");
		return 1;
	}
	if ((arp_resp.oper == htons(2)) && *((in_addr_t*) &arp_resp.spa) == ip) {
		printf("%02X:%02X:%02X:%02X:%02X:%02X\n", arp_resp.sha[0],
				arp_resp.sha[1], arp_resp.sha[2], arp_resp.sha[3],
				arp_resp.sha[4], arp_resp.sha[5]);
	} else {
		printf("Warning: Wrong host\n");
	}

	freeifaddrs(if_addrs);
	close(fd_socket);
	return 0;
}
